/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2009 - 2012 Luca Mingardi.
 *
 * This file is part of jeeObserver.
 *
 * JeeObserver is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * JeeObserver is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */
package jeeobserver;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.logging.Level;
import javax.faces.FacesException;
import javax.faces.FactoryFinder;
import javax.faces.application.Application;
import javax.faces.application.ApplicationFactory;
import javax.faces.component.UICommand;
import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;
import javax.faces.event.ActionListener;
import javax.portlet.PortletContext;
import javax.portlet.PortletRequest;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

/**
 *
 * @author Sebastian Mittelstaedt, Luca Mingardi
 * @version 4.0
 */
public class JsfPortletListener implements ServletContextListener {

	/**
	 *
	 */
	public class PortletActionListenerInvocationHandler implements InvocationHandler {

		private ActionListener actionListener;

		/**
		 *
		 * @param actionListener
		 */
		public PortletActionListenerInvocationHandler(ActionListener actionListener) {
			this.actionListener = actionListener;
		}

		@Override
		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

			final FacesContext facesContext = FacesContext.getCurrentInstance();
			Object externalContextObject = facesContext.getExternalContext();

			PortletContext portletContext = null;

			if (externalContextObject instanceof PortletContext) {
				portletContext = (PortletContext) externalContextObject;
			} else {
				//Workaround for IceFaces
				//Since getExternalContext returns PortletExternalContext, which provides PortletContext via getContext().
				try {
					Method getContextMethod = externalContextObject.getClass().getMethod("getContext");
					Object contextObject = getContextMethod.invoke(externalContextObject);
					if (contextObject instanceof PortletContext) {
						portletContext = (PortletContext) contextObject;
					}
				} catch (Exception e) {
					JeeObserverContext.logger.log(Level.FINE, "Unable to get PortletContext.", e);
				}
			}

			PortletRequest portletRequest = (PortletRequest) facesContext.getExternalContext().getRequest();

			String projectName = portletContext.getPortletContextName();

			if ((projectName == null) || projectName.isEmpty()) {
				projectName = portletRequest.getContextPath();
				projectName = projectName.replace("/", "");
			}

			String action = "";

			ActionEvent event = (ActionEvent) args[0];
			if (event.getComponent() instanceof UICommand) {
				final UICommand uiCommand = (UICommand) event.getComponent();
				if (uiCommand.getActionExpression() != null) {
					action = uiCommand.getActionExpression().getExpressionString();
				} else {
					action = uiCommand.getClass().getName();
				}
			} else {
				JeeObserverContext.logger.log(Level.SEVERE, "Action unknow, component: {0}", event.getComponent().getClass());
			}

			String principal = null;

			try {
				principal = portletRequest.getRemoteUser();
			} catch (final Exception e) {
				JeeObserverContext.logger.log(Level.FINE, "Unable to get caller principal.");
			}

			final ActionObserver observer = new ActionObserver(ActionObserver.CATEGORY_JSF, projectName, action, principal);
			observer.start();

			try {
				Object object = method.invoke(actionListener, args);
				return object;
			} catch (final FacesException exception) {
				observer.throwException(exception);
				throw exception;
			} finally {
				observer.stop();
			}
		}
	}

	/**
	 *
	 * @param servletContextEvent
	 */
	@Override
	public void contextInitialized(ServletContextEvent servletContextEvent) {

		//Get JSF application factory
		ApplicationFactory applicationFactory = (ApplicationFactory) FactoryFinder.getFactory(FactoryFinder.APPLICATION_FACTORY);
		Application application = applicationFactory.getApplication();
		ActionListener defaultActionListener = application.getActionListener();

		//Create proxy for the default actionlistener
		ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
		PortletActionListenerInvocationHandler portletActionListenerInvocationHandler = new PortletActionListenerInvocationHandler(defaultActionListener);

		//@SuppressWarnings("rawtypes")
		Class[] interfaces = new Class[]{ActionListener.class};
		ActionListener actionListenerProxy = (ActionListener) Proxy.newProxyInstance(contextClassLoader, interfaces, portletActionListenerInvocationHandler);

		//Set proxied actionListener as new default actionlistener
		application.setActionListener(actionListenerProxy);
	}

	/**
	 *
	 * @param servletContextEvent
	 */
	@Override
	public void contextDestroyed(ServletContextEvent servletContextEvent) {
	}
}
